
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Collections;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text.RegularExpressions;
using System.Xml;


using Swaf;

namespace Swaf.Container
{
	/// <summary>
	/// Summary description for FlexiMap.
	/// </summary>
	[Serializable]
	public class FlexiMap : ContainerBase, IMap, ISerializable
	{
		protected static Regex pat = new Regex(@"parent\(([^\)]+)\)", RegexOptions.IgnoreCase);
		protected IDictionary m_createPatterns = new ThreadSafeHashtable();

		public FlexiMap()
		{
		}
		public FlexiMap(IDictionary map)
			:base(map, false)
		{
		}

		public FlexiMap(IDictionary map, bool reuseMap)
			:base(map, reuseMap)
		{
		}

		public FlexiMap(string xml)
			:this()
		{
			if(xml != null && xml.Length != 0)
				populateFromXml(xml);
		}

		public FlexiMap(XmlElement xml)
			:this()
		{
			if(xml != null)
				populateFromXml(xml);
		}

		public FlexiMap(Uri xmlLocation)
			:this()
		{
			//TODO: get populated via the uri...
		}

		public override XmlSerializationSupport SerializationSupport {get{return XmlSerializationSupport.AsElement;}}

		public override string Xml
		{get{
			 return getXml("Parent(cache)");
		 }}

		public override string getXml(string options)
		{
			string baseNodeName = "cache";

			Match m = pat.Match(options);
			if(m.Success)
				baseNodeName = m.Groups[1].Value;

			StringWriter strBuf = new StringWriter();
			XmlTextWriter xmlOut = new XmlTextWriter(strBuf);
			xmlOut.WriteStartElement(baseNodeName);
			xmlOut.WriteString("\n");
			foreach(DictionaryEntry de in m_createPatterns)
			{
				if (de.Value != null)
				{
				xmlOut.WriteStartElement("Entry");
				string pattern = (String)de.Value;
				int index = pattern.LastIndexOf('.');
				string name = pattern.Substring(index +1);
				pattern = pattern.Substring(0,index);
				object val = null;
				if(de.Key is INameAddressSupport)
				{
					try 
					{
						val = ((INameAddressSupport)de.Key).get(null);
					}
					catch(Exception){}
				}
				else
					val = de.Key;

				xmlOut.WriteAttributeString("name", name);
				xmlOut.WriteAttributeString("type", val==null?"System.Object":val.GetType().FullName);
				if(pattern != null && pattern.Length != 0)
					xmlOut.WriteAttributeString("nodeType", pattern);
					if (val != null)
					{
				if(de.Key is INameAddressSupport)
					xmlOut.WriteRaw(val.ToString());
				else
					xmlOut.WriteString(val.ToString());
					}
					xmlOut.WriteEndElement();
					xmlOut.WriteString("\n");
				}
			}
			xmlOut.WriteEndElement();
			xmlOut.Close();
			strBuf.Close();
			return strBuf.ToString();
		}

		public void remove(string name) {
			
			if(NameAddress.isSingleAddress(name)) {
                object createPattern = m_fields[name];
                if(createPattern != null)
				    m_createPatterns.Remove(m_fields[name]);
				m_fields.Remove(name);
			}
			else {
				//TODO: Throw new exception.
			}
			
		}

        public void clearXmlCreationPatterns()
        {
            m_createPatterns.Clear();
            foreach (DictionaryEntry de in m_fields)
                m_createPatterns[de.Value] = "." + de.Key;
        }
        
		protected override bool canUseField(string name)
		{
			return true;
		}

		protected override bool canChangeBizFieldType(string name)
		{
			return true;
		}

		public void populateFromXml(string xml)
		{
			XmlDocument doc = new XmlDocument();
			doc.LoadXml(xml);
			populateFromXml(doc.DocumentElement);
		}

		protected void populateFromXml(XmlElement docEl)
		{
			foreach (XmlAttribute att in docEl.Attributes)
				put(att.Name, att.Value);

			foreach(XmlNode node in docEl.ChildNodes)
			{
				if(node is XmlElement)
				{
					XmlElement el = (XmlElement)node;
					string name = "";
					string type = "";
					string nodeType = null;
					object val = null;
					if(el.Name == "Element")
					{
						name = el.GetAttribute("name");
						nodeType = el.GetAttribute("type");
						val = null;
						if(el.FirstChild != null && !(el.FirstChild is XmlText))
							val = el.FirstChild;
						else
							val = el.InnerText;
					}
					else if(el.Name == "Entry")
					{
						name = el.GetAttribute("name");
						type = el.GetAttribute("type");
						bool iss = type.ToLower().CompareTo("string") == 0;
						nodeType = el.GetAttribute("nodeType");
						val = Serialization.deserialize(type, iss?el.InnerText:el.InnerXml);

					}
					if(name != null && name.Length > 0)
					{
						put(name, val, nodeType.Length==0?null:nodeType);
					}
					else
					{
						//Application.currentApp.Log.println(Application.DEFAULT_FRAMEWORK_LOG_LEVEL,"Invalid map data for element.  You must specify both name and type");
						//TODO Throw some kind of exception
					}
				}
			}
		}

		protected override object createNewElement(string keyName, string pattern, object extraInfo)
		{
			string realPattern = Application.currentApp.resMgr.resolve(pattern);
			object realExtraInfo = extraInfo;
			//if(realExtraInfo is String)
			//	realExtraInfo = Application.currentApp.resMgr.resolve((string)extraInfo);
			string oldPattern = null;
			object node = null;
			if(m_fields.Contains(keyName) && m_fields[keyName] != null && m_createPatterns.Contains(m_fields[keyName]))
			{
				oldPattern = m_createPatterns[m_fields[keyName]].ToString();
				m_createPatterns.Remove(m_fields[keyName]);
			}
			try {
				node = base.createNewElement(keyName, realPattern, realExtraInfo);
			}catch(ClassFactoryCreateException e)
			{
				//Intentionally left blank
			}
			if(node == null && realPattern != null && realPattern.Length > 0)
			{
				try 
				{
					//Attempt to use the pattern as a class name.
					node = Cmn.callCtor(realPattern, 
						realExtraInfo==null?new Type[1]{typeof(object)}:new Type[1]{realExtraInfo.GetType()},
						new object[1]{realExtraInfo});
				}
				catch(Exception e)
				{
					throw new ContainerException(string.Format("Unable to create container node for pattern {0}",realPattern),e);
				}
			}

			string pat = pattern + "." + keyName;
			if(node != null)
				m_createPatterns[node] = pat;
			else if(extraInfo is INameAddressSupport)
				m_createPatterns[extraInfo] = pat;
			else if(oldPattern != null)
				m_createPatterns[extraInfo] = oldPattern;

			return node==null?extraInfo:node;
		}


		#region IDictionary Members

		public void Remove(object key)
		{
			this.remove(key.ToString());
		}

		public bool IsReadOnly {get	{return this.isGetOnly;}}

		public IDictionaryEnumerator GetEnumerator()
		{
			return new FlexiMapEnumerator(this);
		}

		public object this[object key]
		{
			get	{return key==null?null:this.get(key.ToString());}
			set {if(key != null)this.put(key.ToString(), value);}
		}

		public bool Contains(object key)
		{
			return this.containsField(key.ToString());
		}

		public void Clear()
		{
			this.m_fields.Clear();
		}

		public ICollection Values {get{return m_fields.Values;}}

		public void Add(object key, object value)
		{
			this.put(key.ToString(), value);
		}

		public ICollection Keys {get{return m_fields.Keys;}}

		public bool IsFixedSize {get{return m_fields.IsFixedSize;}}

		#endregion

		#region ICollection Members

		public bool IsSynchronized {get{return false;}}

		public int Count {get{return m_fields.Count;}}

		public void CopyTo(Array array, int index)
		{
			m_fields.CopyTo(array, index);
		}

		public object SyncRoot {get{return m_fields.SyncRoot;}}

		#endregion

		#region IEnumerable Members

		IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return new FlexiMapEnumerator(this);
		}

		#endregion

		protected class FlexiMapEnumerator : IDictionaryEnumerator
		{
			protected IDictionaryEnumerator m_enum;
			public FlexiMapEnumerator(FlexiMap map)
			{
				m_enum = map.m_fields.GetEnumerator();
			}
			#region IEnumerator Members

			public void Reset()
			{
				m_enum.Reset();
			}

			public object Current
			{
				get
				{
					return m_enum.Current;
				}
			}

			public bool MoveNext()
			{
				return m_enum.MoveNext();
			}

			public DictionaryEntry Entry {get{return m_enum.Entry;}}
			public object Key {get{return m_enum.Key;}}
			public object Value {get{return m_enum.Value;}}

			#endregion

		}
		public FlexiMap(System.Runtime.Serialization.SerializationInfo info, StreamingContext context) : this()
		{
			//m_fields = (IDictionary)info.GetValue("fields", typeof(Swaf.Container.ThreadSafeHashtable));
			SerializationInfoEnumerator e = info.GetEnumerator();
			while (e.MoveNext())
				m_fields[e.Name] = e.Value;
		}
		#region ISerializable Members

		public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, StreamingContext context)
		{
			foreach (DictionaryEntry di in m_fields)
				if (di.Value != null && di.Value.GetType().IsSerializable)
				{
					object k = di.Key;
					object v = di.Value;
					info.AddValue((string)di.Key, di.Value);
					//if (!(di.Value is FlexiMap))
					//{
					//    using (MemoryStream ms = new MemoryStream())
					//    {
					//        IFormatter formatter = new BinaryFormatter();
					//        formatter.Serialize(ms, di.Value);
					//    }
					//}
				}
				
			//info.AddValue("fields", m_fields);
		}

		#endregion
	}
}
